今天要來介紹 Kotlin 中的泛型(Generic)
當我們在寫程式的時候,是不是常常遇到程式碼都一模一樣,但卻可能因為參數的型態,而必須分成不一樣的程式呢?
像是下面這兩段程式,其實內容都一樣,只是參數的型態一個是 String, 一個是 Int
fun acceptString(obj: String): String {
println("acceptString:$obj")
return obj
}
fun acceptInt(obj: Int): Int {
println("acceptString:$obj")
return obj
}
接著來看看如何呼叫這兩個方法
因為這裡想要做的操作是加上 10 在印出結果
val str = acceptString("100")
println("acceptString output:" + str.toInt().plus(10)) // acceptString output:110
val int = acceptInt(100)
println("acceptInt output:" + int.plus(10)) // acceptInt output:110
所以針對第一個 acceptString("100")
,因為得到的是一個字串,所以必須把字串轉成 Int 後再加上 10,也就會是 str.toInt().plus(10)
第二個 acceptInt(100) 一樣是整數,所以直接 int.plus(10)
就可以
當然聰明的我們,是不是就想到可以用 Any 不是嗎?Any 是所有物件的父親
改成這樣後應該都可以成功吧?
fun acceptAny(obj: Any): Any {
println("acceptAny:$obj")
return obj
}
以第一個例子字串來試試看,這裡因為 anything 是 Any 型態,所以根本不能用 toInt()
那試試看用強制轉型 (anything as Int).plus(10)
把 Any 轉成 Int,但竟然會噴錯
這樣成功輸出了,先把 Any 轉成 String 在做 toInt()
val anything = acceptAny("100")
println("any output:" + (anything as String).toInt().plus(10)) // any output:110
所以看了以上的做法是不是覺得很不保險,又很麻煩呢?
所以剛剛的程式改成以下就可以順利執行啦!
fun <T> acceptT(obj: T): T {
println("acceptT:$obj")
return obj
}
// call generic
val t = acceptT("100")
println("generic output:" + t.toInt().plus(10)) // generic output:110
剛剛那樣的宣告方式就是泛型函數
<T>
就是我們要使用的泛型的型態代號,不一定要使用T,要用什麼英文字母都可以,按照以往的慣例會用大寫字母表示,一般來說會用T來宣告。
所以像這裡輸入的參數和回傳值的型態,都是一樣,就都會使用T。
下面的例子,輸入和回傳的型態不一樣,輸入的參數型態是 T,回傳的型態想要是 R,就會像這樣宣告
fun <T, R> acceptTAndReturnR(obj: T): R {
return "test" as R
}
也還有一些慣例的英文命名,像是如果是 key,就會用 K,value 就會用 V
像是 mutableMapOf() 的原始碼,就使用了 K, V 來代表 key, value
這裡宣告了一個 Data 的類別 <V>
就會是泛型的型態,constructor 的變數也是這個泛型型態
使用起來就會像這樣
val dataLong = Data<Long>(1000L)
val dataStr = Data<String>("data test")
來看一下 MutableList 的原始碼,會發現也用了泛型,讓任何資料都可以塞入 MutableList, MutableList 還繼承了 List 和 MutableCollection 這兩個介面。
上界(upper bound)
像是下例,可以針對 T 這個泛型型態限制的上界
就會是 Number,也就是 T 可以是 Int, Long, Double...等型態來做加總,最後回傳 T型態的結果
fun <T: Number> sum(num1: T, num2: T) :T {
return num1.toDouble().plus(num2.toDouble()) as T
}
如果給了 String 是不合法的!
上界(upper bound)
就要使用 where
fun <T> appendDot(seq: T): T
這部分是正常泛型宣告
where T : CharSequence, T : Appendable
這部分就定義了兩個上界,限定 T 要是 CharSequence
或 Appendable
這兩個 interface 的物件實作,才能傳入這個函數
fun <T> appendDot(seq: T): T
where T : CharSequence, T : Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
return seq
}
因為下面這兩個都是 class 因為只允許單一繼承,所以不行,上界中 class 只能一個,要多個要用介面。
其實所有的泛型都有一個隱藏的上界,那就是 Any?
這樣的上界會有什麼需要注意的呢?
像這樣的程式,因為隱藏的上界是 Any? ,所以會提示說要用 ?.,因為 obj1 會是 nullable 的型態
修正後會是這樣
fun <T> compareObj(obj1: T, obj2: T): Boolean? {
return obj1?.equals(obj2)
}
如果不想處理 nullable,可以把上界直接設成 Any
fun <T : Any> compareObj2(obj1: T, obj2: T): Boolean? {
return obj1.equals(obj2)
}
以上就是今天的內容!謝謝大家!
泛型還有 out, in 和 reified 這三個東西,明天再繼續探討!
今日練習的程式在這: 請點我